package com.rtsapp.server.common;

import com.rtsapp.server.utils.StringUtils;
import com.rtsapp.server.logger.Logger;

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by admin on 15-10-12.
 * 定时任务执行
 * 1. 添加一个执行任务, 立即执行，或指定时间后回调
 * 2. 为策划添加一个周期性管理的方法, 可以模拟天等周期
 *
 * 管理调度任务
 *  1. 有些任务在服务器启动时就已经确定每天的指定时间调度
 *      1.每天指定一个时间调度, 如凌晨4点发放竞技场奖励
 *      2.每天指定几个时间调度, 如每天10点起每半小时自动发放军团副本奖励
 *  2. 有些任务在业务层面对某个对象动态的加入任务并且顶替未完成的相同类型的旧业务层面加入的任务, 比如10分钟后的下次免费银币抽奖红点
 *  3. 有些任务在业务层面对某个对象动态的加入任务除非有未完成的相同类型的旧任务, 比如48小时后的下次免费金币抽奖红点
 *
 *
 * 设计思路
 *  1. 调度线程在所有调度任务加入前启动
 *  2. 名次解释
 *      1. 模块类型:    加入这个任务的所属模块, 细分的, 如银币免费抽奖红点
 *      2. 任务类型:    每天指定时间调度或者业务层加入的调度
 *      3. 时间:       调度任务的时间
 *      4. 任务体:      任务内容,包括已经加入的任务管理对象和任务调度对象
 *  3. 数据结构Map<模块类型, Map<任务类型, Map<时间,任务体>>>
 *  4. 加入任务有以下几种途径
 *      1. 加入每天指定一个时间调度(每天几个时间调度可以通过多此加入实现)
 *      2. 加入业务层的调度任务, 并顶替未完成的相同类型业务层调度任务
 *      3. 加入业务层的调度任务, 除非用未完成的相同类型的旧任务
 *  5. 新任务加入流程
 *      1. (4.1) 检查Map<模块类型, Map<固定任务>>里是否有相同时间的任务[如果有则关闭旧任务],加入新任务
 *      2. (4.2) 检查Map<模块类型, Map<业务任务>>里是否有相同时间的任务[如果有则关闭旧任务],加入新任务
 *      2. (4.3) 检查Map<模块类型, Map<所有类型>>里是否有相同时间的任务, 如果有则取消
 *  6. 调用方式
 *      1. (4.1)
 *  每个模块类型的每个任务类型的每个时间最多只有一个任务体
 *  */
public final class Timer implements Runnable {

    private static final Logger LOGGER =com.rtsapp.server.logger.LoggerFactory.getLogger( Timer.class );
    private static final long DEFAULT_TIMER_SHUTDOWN_TIMEOUT = 2 * 60 ;//2分钟


    private static Timer instance = new Timer();

    public static Timer getInstance() {
        return instance;
    }

    private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread( r , "Timer");
            return t;
        }
    });


    private boolean started = false;
    private List<Task> taskList = new ArrayList<>();


    /**
     * 一次性执行的任务管理器
     */
    private TaskManager onceTaskManager = new TaskManager();
    /**
     * 重复执行的任务管理器
     */
    private TaskManager repeatTaskManager = new TaskManager();

    /**
     * 不管调用多少次, timer只能真被启动一次
     */
    public synchronized  void start() {
        if( ! started ) {
            started = true;
            run();
        }
    }


    /**
     * 停止Timer
     * 这个方法在Application.stop时会被调用,其他地方不要去掉用它
     */
    public void stop(){
        try {
            executor.shutdownNow();
            boolean isTerminate =  executor.awaitTermination( DEFAULT_TIMER_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS );
            if( isTerminate ) {
                LOGGER.info( "Timer已经完全停止" );
            }else{
                LOGGER.info( "Timer在超时前并未完全停止" );
            }
        }catch( Throwable ex ){
            LOGGER.error( "Timer.stop()发生错误.", ex );
        }
    }

    /**
     * run方法,定义不能被重写
     */
    public final void run() {
        for (Task task : taskList) {
            String[] times = task.time.split(":");
            Calendar date = new GregorianCalendar();
            date.set(Calendar.HOUR_OF_DAY, StringUtils.toInt32(times[0]));
            date.set(Calendar.MINUTE, StringUtils.toInt32(times[1]));
            date.set(Calendar.SECOND, 0);
            date.set(Calendar.MILLISECOND, 0);
            long timeDifference = date.getTimeInMillis() - System.currentTimeMillis();
            if (timeDifference < 0) {
                date.add(Calendar.DAY_OF_MONTH, 1);
                timeDifference = date.getTimeInMillis() - System.currentTimeMillis();
            }
            executor.schedule(task.command, timeDifference, TimeUnit.MILLISECONDS);
        }
        executor.schedule(this, 24 * 60 * 60 * 1000, TimeUnit.MILLISECONDS);
    }

    /**
     * 在启动Timer前加入调度任务
     * @param command
     * @param time
     */
    public void putSchedule(Runnable command, String time) {

        if( started ){
            throw new RuntimeException( "putSchedule只能在Timer.start()前调用" );
        }

        taskList.add(new Task(command, time));
    }


    /**
     * 调度任务执行一次,
     * 不判断任务是否重复, 只要调用该方法，任务就会被调度, 可以通过ScheduledFuture取消该任务
     * @param command 任务
     * @param delay 延迟时间(从当前时间算起)
     * @param unit 时间的单位( 后续打算把这个字段去掉, 统一只能以秒作为单位 )
     * @return
     */
    public ScheduledFuture<?>  schedule( Runnable command, long delay, TimeUnit unit ){
        return onceTaskManager.scheduleOnce(executor, false, command, delay, unit);
    }


    /**
     * 按固定频率执行指定任务
     * 不判断任务是否重复, 只要调用该方法任务就会被调度
     * @param command 任务
     * @param initialDelay 第一次执行的延迟时间
     * @param period 两次执行之间的周期时间
     * @param unit 时间单位
     * @return
     */
    public synchronized ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit){
        return repeatTaskManager.scheduleAtFixedRate(executor, false, command, initialDelay, period, unit);
    }


    /**
     * 查询具有等价身份的任务:
     * 任务使用equals和hashcode来判断是否等价
     * @param command
     * @return
     */
    public ScheduledFuture<?> searchUniqueTask( Runnable command ){
        return onceTaskManager.searchHashTask(command);
    }


    /**
     * 执行一次调度, 并加入身份
     * @param command
     * @param delay
     * @param unit
     * @return
     */
    public ScheduledFuture<?>  scheduleUnique( Runnable command, long delay, TimeUnit unit ){
        return onceTaskManager.scheduleOnce(executor, true, command, delay, unit);
    }


    /**
     * 查询具有等价身份的重复执行任务
     * @param command
     * @return
     */
    public ScheduledFuture<?> searchUniqueFixedRateTask( Runnable command ){
        return repeatTaskManager.searchHashTask(command);
    }


    /**
     *
     * @param command
     * @param initialDelay
     * @param period
     * @param unit
     * @return
     */
    public synchronized ScheduledFuture<?> scheduleUniqueAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit){
        return repeatTaskManager.scheduleAtFixedRate(executor, true, command, initialDelay, period, unit);
    }



    /**
     * 任务管理器
     * 1. 生成任务id
     * 2. 记录所有当前任务
     * 3. 查询当前的任务
     */
    private static class TaskManager{

        /**
         * 任务id生成器
         */
        private final AtomicLong taskIdGenerator = new AtomicLong( 0 );
        /**
         * 监控所有正在执行的任务, 以及每个任务执行的时间
         */
        private final ConcurrentMap<Long, TimerInnerTask> idTasks = new ConcurrentHashMap<>( );

        private final ConcurrentHashMap<Runnable, TimerInnerTask> hashTasks = new ConcurrentHashMap<>();


        public TaskManager(){

        }


        /**
         * 按equals，hash查询等价的任务
         * @param command
         * @return
         */
        public ScheduledFuture<?> searchHashTask( Runnable command ){
            TimerInnerTask innerTask =  hashTasks.get(command);
            if( innerTask == null ){
                return null;
            }

            return innerTask.getFuture();
        }
        
        /**
         * 固定频率重复执行任务
         * @param executor 在哪个executor中执行
         * @param unique 是否重复
         * @param command
         * @param initialDelay
         * @param period
         * @param unit
         * @return
         */
        public  ScheduledFuture<?> scheduleAtFixedRate( ScheduledExecutorService executor,  boolean unique,  Runnable command, long initialDelay, long period, TimeUnit unit){

            TimerInnerTask task =  new TimerInnerTask( TaskType.Repeat, unique, getNextTaskId(), command, initialDelay, period, unit );

            ScheduledFuture<?> future =  executor.scheduleAtFixedRate( task, initialDelay, period, unit);

            return  this.putTask(task, future);
        }


        /**
         * 一次性执行任务
         * @param executor
         * @param command
         * @param delay
         * @param unit
         * @return
         */
        public ScheduledFuture<?>  scheduleOnce( ScheduledExecutorService executor,  boolean unique, Runnable command, long delay, TimeUnit unit ){

            TimerInnerTask task = new TimerInnerTask(TaskType.Once, unique, getNextTaskId(), command, delay, 0, unit );

            ScheduledFuture<?> future =  executor.schedule(task, delay, unit);

            return this.putTask(task, future);
        }



        /**
         * 将任务和Future建立关闭, 并将任务加入任务集合中
         * @param task
         * @param future
         * @return
         */
        private ScheduledFuture<?> putTask(TimerInnerTask task, ScheduledFuture<?> future) {
            TimerInnerScheduledFuture futureForTimer = new TimerInnerScheduledFuture( future, task );
            task.setFuture(futureForTimer);

            idTasks.put(task.getTaskId(), task);

            if(  task.isUnique() ) {
                hashTasks.put(task.getTask(), task);
            }

            return futureForTimer;
        }


        /**
         * 从任务管理器移除一个任务,这里并不停止任务，只是任务和任务管理器失去关联
         * 请使用ScheduledFuture 来关闭任务
         * @param task
         */
        private void removeTask( TimerInnerTask task ){

            // 只能被移除一次, 一个任务可能先被cancel,此时它会立即执行移除, 但是任务可能依然在执行，执行完毕后可能再次调用remove，造成错误的移除自身以外的对象
            if( task.isRemovedFromTaskManager() ){
                return;
            }

            task.setRemovedFromTaskManager( true );

            idTasks.remove(task.getTaskId());
            if( task.isUnique() ) {
                hashTasks.remove(task.getTask());
            }

        }

        /**
         * 获得下一个任务Id
         * 任务Id自增
         * 如果这个id被占用,再取下一个
         * 如果任务Id到达了最大值，从1开始,
         *
         * TODO  这个理论不会造成死循环, 代码怎么标记下这个问题比较合适
         * @return
         */
        private long getNextTaskId(){

            long taskId;

            do {

                taskId = taskIdGenerator.incrementAndGet();
                if( taskId == Long.MAX_VALUE ){
                    taskIdGenerator.set( 0 );
                }

            }while(  idTasks.containsKey( taskId ) );

            return taskId;
        }




        /**
         * 任务类型
         */
        private  enum TaskType{
            /**
             * 一次性的任务
             */
            Once,
            /**
             * 重复执行的任务
             */
            Repeat,
        }


        /**
         * 执行任务封装
         */
        private  class TimerInnerTask implements Runnable{

            private final TaskType taskType;
            /**
             * 是否具有唯一性
             */
            private final boolean unique;
            private final long taskId;

            private final Runnable task;
            private final long submitMillis;
            private final long initDelayMillis;
            private final long periodMillis;
            private ScheduledFuture future;

            private boolean removedFromTaskManager = false;


            public TimerInnerTask( TaskType taskType, boolean unique,  long taskId, Runnable task, long initDelay, long period, TimeUnit unit){

                this.taskType = taskType;
                this.unique = unique;

                this.taskId = taskId;
                this.task = task;
                this.submitMillis = System.currentTimeMillis();
                this.initDelayMillis = unit.toMillis( initDelay );
                this.periodMillis = unit.toMillis( period );
            }

            @Override
            public void run() {
                try{
                    task.run();
                }catch ( Throwable ex ){
                    LOGGER.error( "任务执行失败: 任务id:" + taskId + ", 任务对象:" + task , ex );
                }finally {
                    //如果是一次性任务, 任务已经结束
                    if( taskType == TaskType.Once) {
                        removeTask( this );
                    }
                }
            }

            public long getTaskId() {
                return taskId;
            }

            public Runnable getTask() {
                return task;
            }

            public long getSubmitMillis() {
                return submitMillis;
            }

            public long getInitDelayMillis() {
                return initDelayMillis;
            }

            public long getPeriodMillis() {
                return periodMillis;
            }

            public void setFuture(ScheduledFuture future) {
                this.future = future;
            }

            public ScheduledFuture getFuture() {
                return future;
            }

            public boolean isUnique() {
                return unique;
            }

            public boolean isRemovedFromTaskManager() {
                return removedFromTaskManager;
            }

            public void setRemovedFromTaskManager(boolean removedFromTaskManager) {
                this.removedFromTaskManager = removedFromTaskManager;
            }
        }


        /**
         * 任务的Schedule封装
         * @param <T>
         */
        private  class TimerInnerScheduledFuture<T> implements ScheduledFuture<T> {

            private final ScheduledFuture<T> innerFuture;
            private final TimerInnerTask task;

            public TimerInnerScheduledFuture(ScheduledFuture f, TimerInnerTask task){
                this.innerFuture = f;
                this.task = task;
            }

            @Override
            public long getDelay(TimeUnit unit) {
                return innerFuture.getDelay( unit );
            }

            @Override
            public int compareTo(Delayed o) {
                return innerFuture.compareTo(o);
            }

            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {

                //如果cancel失败，一般是任务已经执行完毕啦，或者之前已经被cancel啦
                boolean result =  innerFuture.cancel( mayInterruptIfRunning );

                //还是继续调用removeTask
                removeTask( task );

                return result;
            }

            @Override
            public boolean isCancelled() {
                return innerFuture.isCancelled();
            }


            @Override
            public boolean isDone() {
                return innerFuture.isDone();
            }

            @Override
            public T get() throws InterruptedException, ExecutionException {
                return innerFuture.get();
            }

            @Override
            public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
                return innerFuture.get( timeout , unit );
            }





        }

    }



    /**
     * 可重复执行的任务
     *
     */
    private static class Task {
        Runnable command;
        String time;

        public Task(Runnable command, String time) {
            this.command = command;
            this.time = time;
        }
    }




}

